package com.cyf.plc.plc.client

import com.cyf.library.util.loge
import com.cyf.library.util.logi
import com.cyf.plc.constant.Constants
import com.cyf.plc.constant.Constants.Companion.DBNumber
import com.cyf.plc.plc.PlcExecutor
import com.cyf.plc.bean.PlcWriteAttribute

import org.snap7.Moka7.S7
import org.snap7.Moka7.S7Client
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean

/**
 * PLC客户端
 */
class PlcClient(plcIp: String, startReadAddress: Int, maxBufferSize: Int) : Runnable, PlcClientInter {
    //S7连接客户端
    private val s7Client: S7Client?

    //PLC ip地址
    private val plcIp: String

    //起始读取地址
    private var startReadAddress: Int

    //缓存数据
    private var buffer: ByteArray
    private var preBuffer: ByteArray

    //是否连接了plc
    private val isConnected = AtomicBoolean(false)

    //读取间隔,单位:ms
    private var readInterval = 300

    //最后的错误码
    private var lastError = 0

    //写入队列
    private val writeBlockingQueue: Queue<PlcWriteAttribute> = LinkedList()

    /**
     * 重新设置起始地址，和读取长度
     */
    fun setStartReadAddress(startReadAddress: Int, readLength: Int) {
        this.startReadAddress = startReadAddress
        this.buffer = ByteArray(readLength)
        this.preBuffer = ByteArray(readLength)
    }

    /**
     * 放入队列
     *
     * @param plcWriteAttribute 写入寄存器的对象
     */
    fun putWriteBean(plcWriteAttribute: PlcWriteAttribute) {
        //超过最大队列长度，则丢弃,防止队列溢出
        if (writeBlockingQueue.size >= MAX_QUEUE_LENGTH) writeBlockingQueue.clear()
        //存入队列
        writeBlockingQueue.offer(plcWriteAttribute)
    }

    override fun run() {
        //程序没有崩溃，就一直执行
        while (!Thread.interrupted()) {
            //设置plc连接状态
            isConnected.set(s7Client!!.Connected)
            //没有连接，尝试连接
            if (!isConnected.get()) {
                lastError = s7Client.ConnectTo(
                    plcIp,
                    Constants.rack,
                    Constants.slot
                )
                if (lastError > 0) {
                    (" S7Client connect error!" + S7Client.ErrorText(
                        lastError
                    )).loge()
                    closeS7Client()
                } else {
                    ("S7Client connect success!Connected to   : " + plcIp + " (Rack=" + Constants.rack + ", Slot=" + Constants.slot + ")").logi()
                }
            }
            //连接成功，读取缓存
            if (isConnected.get()) {
                val plcWriteAttribute = writeBlockingQueue.poll()
                if (plcWriteAttribute != null) {
                    var result = false
                    when (plcWriteAttribute.addressUnit) {
                        Constants.VB -> result =
                            PutByteAt(plcWriteAttribute.offset, plcWriteAttribute.value.toByte())
                        Constants.VW -> result =
                            PutShortAt(plcWriteAttribute.offset, plcWriteAttribute.value.toInt())
                        Constants.VD -> result =
                            PutFloatAt(plcWriteAttribute.offset, plcWriteAttribute.value)
                    }
                    "S7Client write result:$result,write bean:$plcWriteAttribute".logi()
                }
                lastError =
                    s7Client.ReadArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
                if (lastError > 0) {
                    "S7Client error:" + S7Client.ErrorText(lastError).loge()
                    closeS7Client()
                } else {
                    //更新plc读取列表
                    if (!compareBufferEqual()){
                        System.arraycopy(buffer,0,preBuffer,0,preBuffer.size)
                        PlcExecutor.updatePlcFault()
                        PlcExecutor.updatePlcSystem()
                        PlcExecutor.plcUpdateSetting()
                    }
//                    PlcExecutor.updatePlcSingleAddressList()
//                    "S7Client read result:" + buffer.contentToString().logi()
                }
            }
            //plc每隔100ms，读取1次数据
            try {
                TimeUnit.MILLISECONDS.sleep(readInterval.toLong())
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
        }
    }

    fun compareBufferEqual(): Boolean{
        for (index in preBuffer.indices) {
            if (preBuffer[index]!=buffer[index]){
                return false
            }
        }
        return true
    }

    override fun GetDWordAt(address: Int): Long {
        return S7.GetDWordAt(buffer, address)
    }

    override fun GetDIntAt(address: Int): Int {
        return S7.GetDIntAt(buffer, address)
    }

    override fun GetFloatAt(address: Int): Float {
        return S7.GetFloatAt(buffer, address)
    }

    override fun GetShortAt(address: Int): Int {
        return S7.GetShortAt(buffer, address)
    }

    override fun GetWordAt(address: Int): Int {
        return S7.GetWordAt(buffer, address)
    }

    override fun GetByteAt(address: Int): Byte {
        return buffer[address]
    }

    override fun PutDWordAt(address: Int, value: Long): Boolean {
        var result = false
        S7.SetDWordAt(buffer, address, value)
        synchronized(s7Client!!) {
            lastError =
                s7Client.WriteArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
            if (lastError == 0) result = true
        }
        return result
    }

    override fun PutDIntAt(address: Int, value: Int): Boolean {
        var result = false
        S7.SetDIntAt(buffer, address, value)
        synchronized(s7Client!!) {
            lastError =
                s7Client.WriteArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
            if (lastError == 0) result = true
        }
        return result
    }

    override fun PutFloatAt(address: Int, value: Float): Boolean {
        var result = false
        S7.SetFloatAt(buffer, address, value)
        synchronized(s7Client!!) {
            lastError =
                s7Client.WriteArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
            if (lastError == 0) result = true
        }
        return result
    }

    override fun PutShortAt(address: Int, value: Int): Boolean {
        var result = false
        S7.SetShortAt(buffer, address, value)
        synchronized(s7Client!!) {
            lastError =
                s7Client.WriteArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
            if (lastError == 0) result = true
        }
        return result
    }

    override fun PutWordAt(address: Int, value: Int): Boolean {
        var result = false
        S7.SetWordAt(buffer, address, value)
        synchronized(s7Client!!) {
            lastError =
                s7Client.WriteArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
            if (lastError == 0) result = true
        }
        return result
    }

    override fun PutByteAt(address: Int, value: Byte): Boolean {
        var result = false
        buffer[address] = value
        synchronized(s7Client!!) {
            lastError =
                s7Client.WriteArea(S7.S7AreaDB, DBNumber, startReadAddress, buffer.size, buffer)
            if (lastError == 0) result = true
        }
        return result
    }

    override fun closeS7Client() {
        writeBlockingQueue.clear()
        s7Client?.Disconnect()
    }

    companion object {
        //队列的最大长度
        private const val MAX_QUEUE_LENGTH = 10
    }

    //构造函数
    init {
        buffer = ByteArray(maxBufferSize)
        preBuffer = ByteArray(maxBufferSize)
        s7Client = S7Client()
        s7Client.SetConnectionType(S7.OP.toShort())
        this.startReadAddress = startReadAddress
        this.plcIp = plcIp
    }
}